Quartz
Quartz 是一个功能强大、成熟且应用广泛的开源作业调度库,它允许按照极其复杂的计划来执行任务。与Spring Boot自带的 @Scheduled注解(适用于简单、静态任务)相比,Quartz 的核心优势在于:
- 持久化存储:可以将作业、触发器、调度信息存储到数据库中,确保在应用重启后任务状态不丢失,并支持集群部署
- 动态管理:可以在运行时动态地创建、修改、暂停、恢复和删除任务
- 灵活调度:通过强大的 Cron表达式 提供极为复杂的调度计划(如“每个工作日上午9:15,但节假日除外”)
- 集群与故障转移:支持多节点集群运行,实现负载均衡和任务故障自动转移,保证高可用性。
核心对象
在Spring Boot项目中集成Quartz,主要涉及以下几个核心对象:
Scheduler(调度器):- 核心控制器,由它来绑定
JobDetail和Trigger,并实际调度任务的执行。
- 核心控制器,由它来绑定
Job(任务):定义具体要执行的工作内容。需要创建一个类实现org.quartz.Job接口,并重写execute方法- Spring集成关键:在Spring Boot中,
Job类通常是一个普通的Spring Bean,以便能通过依赖注入使用其他服务(如Service、Repository)。但需要通过SpringBeanJobFactory来支持
- Spring集成关键:在Spring Boot中,
JobDetail(任务详情):定义Job的实例详情。它包含了Job执行时所需的各种属性。一个Job类可以有多个JobDetail实例,每个实例可以有不同的参数Trigger(触发器):定义任务何时被触发执行的规则。最常用的是基于Cron表达式的CronTrigger- 一个
JobDetail可以关联多个Trigger,一个Trigger只能关联一个JobDetail
- 一个
基本使用
application.yml
yaml
spring:
quartz:
job-store-type: jdbc # 使用JDBC存储(持久化)。默认为 memory,即内存存储
jdbc:
initialize-schema: always # 自动初始化Quartz所需的数据库表(首次启动时使用)
properties:
org.quartz.scheduler.instanceId: AUTO # 实例ID自动生成,集群时需要
org.quartz.jobStore.isClustered: true # 开启集群支持创建Job:
java
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import java.time.LocalDateTime;
// 实现 Job 接口的类
public class MyDynamicJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("执行业务逻辑..." + LocalDateTime.now());
}
}动态管理:
java
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
@Service
public class DynamicJobService implements CommandLineRunner {
@Autowired
private Scheduler scheduler;
public void addNewJob(String jobName, String cronExpression) throws SchedulerException {
// 检查任务是否已存在
if (scheduler.checkExists(JobKey.jobKey(jobName))) {
System.out.println("任务已存在: " + jobName);
return;
}
JobDetail jobDetail = JobBuilder.newJob(MyDynamicJob.class)
.withIdentity(jobName)
.storeDurably()
.usingJobData("jobName", jobName) // 可以传递参数
.build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger-" + jobName)
.withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))
.build();
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("任务添加成功: " + jobName + ", Cron: " + cronExpression);
// 立即打印所有任务
printAllJobs();
}
public void pauseJob(String jobName) throws SchedulerException {
JobKey jobKey = JobKey.jobKey(jobName);
if (scheduler.checkExists(jobKey)) {
scheduler.pauseJob(jobKey);
System.out.println("任务已暂停: " + jobName);
} else {
System.out.println("任务不存在: " + jobName);
}
}
@Override
public void run(String... args) throws Exception {
try {
// 延迟3秒,确保调度器完全启动
Thread.sleep(3000);
System.out.println("开始添加测试任务...");
addNewJob("test", "* * * * * ?");
// 10秒后暂停任务
new Thread(() -> {
try {
Thread.sleep(10000);
pauseJob("test");
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}).start();
} catch (SchedulerException | InterruptedException e) {
e.printStackTrace();
}
}
private void printAllJobs() throws SchedulerException {
System.out.println("=== 当前所有Quartz任务 ===");
for (String groupName : scheduler.getJobGroupNames()) {
for (JobKey jobKey : scheduler.getJobKeys(GroupMatcher.jobGroupEquals(groupName))) {
System.out.println("任务: " + jobKey.getName() + ", 组: " + groupName);
}
}
System.out.println("=======================");
}
}Ruoyi定时任务
执行策略
- 立即执行:所有的
misfire会马上执行——如果9点misfire,在10:15系统恢复,9点,10点的misfire会马上执行(该策略有可能会造成短期内大量定时任务蜂拥执行) - 执行一次:所有的合并
misfire执行一次——如果9点misfire,在10:15系统恢复,只会执行一次misfire,然后等待下一周期 - 放弃执行:所有的
misfire放弃执行,等待下一周期的任务——如果9点misfire,在10:15系统恢复,需要等待下一周期11点才执行
对应代码:
java
/**
* 设置定时任务策略
*/
public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb)
throws TaskException
{
switch (job.getMisfirePolicy())
{
case ScheduleConstants.MISFIRE_DEFAULT:
return cb;
case ScheduleConstants.MISFIRE_IGNORE_MISFIRES:
return cb.withMisfireHandlingInstructionIgnoreMisfires();
case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED:
return cb.withMisfireHandlingInstructionFireAndProceed();
case ScheduleConstants.MISFIRE_DO_NOTHING:
return cb.withMisfireHandlingInstructionDoNothing();
default:
throw new TaskException("The task misfire policy '" + job.getMisfirePolicy()
+ "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR);
}
}